/*
 * User: ophir
 * Date: Sep 5, 2008
 * Time: 11:11:36 AM
 */
package org.conann.container;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.ReferenceType.SOFT;
import static com.google.common.base.ReferenceType.STRONG;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.ReferenceMap;
import org.conann.components.SimpleBean;
import org.conann.configuration.Configuration;
import org.conann.exceptions.WebBeansMetadataException;
import org.conann.exceptions.WebBeansSystemException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.webbeans.Bean;
import java.util.Map;
import java.util.Set;

public class BeansRegistry {

    private static final Logger log = LoggerFactory.getLogger(BeansRegistry.class);

    private final Configuration configuration;
    private final Map<Class<?>, Bean<?>> beans = new ReferenceMap<Class<?>, Bean<?>>(SOFT, STRONG);
    private final Multimap<Class<?>, Bean<?>> conformingBeans = Multimaps.newHashMultimap();
    private final Map<String, Bean<?>> beansByName = new ReferenceMap<String, Bean<?>>(SOFT, STRONG);

    public BeansRegistry(Configuration configuration) {
        checkNotNull(configuration);
        this.configuration = configuration;
    }

    @SuppressWarnings({"unchecked"})
    public <T> Bean<T> getComponent(Class<T> type) {
        checkNotNull(type);
        return (Bean<T>) beans.get(type);
    }

    public <T> void addSimpleComponent(Class<T> type) {
        checkNotNull(type);
        if (beans.containsKey(type)) {
            throw new WebBeansSystemException("Bean type [%s] is already registered.", type);
        }
        SimpleBean<T> component = new SimpleBean<T>(configuration, type);
        beans.put(type, component);

        registerByName(component);

        for (Class<?> apiType : component.getTypes()) {
            conformingBeans.put(apiType, component);
        }
        log.debug("Added component: {}", type.getName());
    }

    /** If the component has a name, register it by its name so we can easily resolve it by its name later on. */
    private <T> void registerByName(Bean<T> bean) {
        String componentName = bean.getName();
        if (componentName != null) {
            if (beansByName.containsKey(componentName)) {
                Bean<?> alreadyRegisteredBean = beansByName.get(componentName);
                throw new WebBeansMetadataException("Error while trying to register bean of type [%s] - bean name [%s] is already used by [%s].", bean.getImplClass(), componentName, alreadyRegisteredBean.getImplClass().getName());
            }
            beansByName.put(componentName, bean);
        }
    }

    /**
     Get all components whose types implement the specified type.

     @param apiType The type (class or interface) for which implementing components are required.
     @return A set of components that their types implement the specified type.
     */
    public Set<Bean<?>> getConformingBeans(Class<?> apiType) {
        checkNotNull(apiType);
        return (Set<Bean<?>>) conformingBeans.get(apiType);
    }

    public boolean contains(Class<?> componentType) {
        checkNotNull(componentType);
        return beans.containsKey(componentType);
    }
}